package cn.donting.plugin.spring.boot.starter;

import cn.donting.plugin.spring.boot.starter.dev.DevPluginClassLoader;
import cn.donting.plugin.spring.boot.starter.dev.DevPluginLoader;
import cn.donting.plugin.spring.boot.starter.dev.DevPluginRun;
import cn.donting.plugin.spring.boot.starter.exception.PluginInstallException;
import cn.donting.plugin.spring.boot.starter.exception.PluginLoadException;
import cn.donting.plugin.spring.boot.starter.exception.PluginUpdateException;
import cn.donting.plugin.spring.boot.starter.properties.PluginProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;

/**
 * 插件管理器，注入主程序spring
 * @author donting
 */
@Slf4j
@Component
public class DefaultPluginManger implements PluginManger , ApplicationRunner {

    @Autowired
    PluginProperties pluginProperties;

    @Autowired
    PluginDb pluginDb;

    @Autowired
    ApplicationContext applicationContext;

    @Autowired(required = false)
    DevPluginLoader devPluginLoader;

    /**
     * 插件map Id,Plugin
     */
    protected HashMap<String, PluginWrapper> pluginWrapperHashMap = new HashMap<>();


    @Override
    public PluginWrapper install(byte[] bytes) throws IOException, PluginLoadException, PluginInstallException {
        String pluginFileName=UUID.randomUUID().toString();
        File file = new File(pluginProperties.getPath() + File.separator + pluginFileName + ".jar");
        FileOutputStream outputStream = new FileOutputStream(file);
        outputStream.write(bytes);
        outputStream.flush();
        outputStream.close();
        PluginLoader pluginLoader = getPluginLoader(file);
        PluginWrapper plugin;
        try {
            plugin = pluginLoader.load(file);
        } catch (PluginLoadException ex) {
            log.warn("delete " + file.getPath());
            file.delete();
            throw new PluginLoadException(ex.getMessage(), "不是一个插件", ex.getCause());
        }

        //是否已安装
        if (pluginDb.isInstall(plugin.getPlug().id())) {
            file.delete();
            log.warn("pluginId " + plugin.getPlug().id() + " name:" + plugin.getPlug().name() + " 已安装");
            throw new PluginInstallException("pluginId " + plugin.getPlug().id() + " name:" + plugin.getPlug().name() + " is install",
                    "pluginId " + plugin.getPlug().id() + " name:" + plugin.getPlug().name() + " 已安装");
        }
        try {
            //数据存储
            pluginDb.install(plugin.getPlug(), file);
        } catch (Exception ex) {
            log.error("db 存储异常");
            log.error(ex.getMessage(), ex);
            file.delete();
            throw ex;
        }
        //创建数据目录
        File pluginData = new File(pluginProperties.getPath()+File.separator+"data"+File.separator+pluginFileName);
        if (!pluginData.exists()) {
            pluginData.mkdirs();
        }

        plugin.setDataPath(pluginData.getPath());

        //启动
        pluginWrapperHashMap.put(plugin.getPlug().id(), plugin);
        plugin.install();
        return plugin;

    }

    @Override
    public PluginWrapper update(byte[] bytes, String pluginId) throws IOException, PluginLoadException, PluginUpdateException {
        File file = new File(pluginProperties.getPath() + File.separator + UUID.randomUUID().toString() + ".jar");
        FileOutputStream outputStream = new FileOutputStream(file);
        outputStream.write(bytes);
        outputStream.flush();
        outputStream.close();
        PluginLoader pluginLoader = getPluginLoader(file);
        PluginWrapper plugin = pluginLoader.load(file);
        //跟新id是否一致
        if (plugin.getPlug().id().equals(pluginId)) {
            //是否安装
            if (pluginDb.isInstall(pluginId)) {
                PluginDbInfo dbInfo = pluginDb.read().get(pluginId);
                if (dbInfo.getVersionCode() == plugin.getPlug().versionCode()) {
                    file.delete();
                    log.warn("安装版本一致： versionCode:" + dbInfo.getVersionCode());
                    throw new PluginUpdateException("version  inconsistency，not update", "与安装版本一致，无法更新");
                }
                if (dbInfo.getVersionCode() > plugin.getPlug().versionCode()) {
                    file.delete();
                    log.warn("安装版本大于更新版本，无法更新。versionCode {} > {}", dbInfo.getVersionCode(), plugin.getPlug().versionCode());
                    throw new PluginUpdateException("Installation version is greater than update version, unable to update", "安装版本大于更新版本，无法更新。");
                }
                try {
                    pluginDb.update(plugin.getPlug(), file);
                    if (pluginWrapperHashMap.containsKey(pluginId)) {
                        //在运行,更新
                        plugin.start();
                        pluginWrapperHashMap.put(pluginId, plugin);
                    }
                    return plugin;
                } catch (IOException ex) {
                    log.error("db 存储异常");
                    log.error(ex.getMessage());
                    throw ex;
                }
            } else {
                log.warn("插件未安装,无法更新。pluginId:{}", pluginId);
                throw new PluginUpdateException("plugin is not install。pluginId:" + pluginId, "插件未安装,无法更新pluginId:" + pluginId);
            }
        } else {
            log.warn("new plug id:" + plugin.getPlug().id() + " old plug id:" + pluginId + " \n 更新源不一致");
            throw new PluginUpdateException("new plug id:" + plugin.getPlug().id() + " old plug id:" + pluginId + " \n 更新源不一致");
        }


    }

    @Override
    public List<PluginDbInfo> getInstallInfo() throws IOException {
        Map<String, PluginDbInfo> read = pluginDb.read();
        List<PluginDbInfo> list = new ArrayList<>(read.values());
        return list;
    }

    @Override
    public PluginWrapper getRunningPlug(String pluginId) {
        return pluginWrapperHashMap.get(pluginId);
    }

    @Override
    public boolean containsPlugInstall(String pluginId) throws IOException {
        return pluginDb.read().containsKey(pluginId);
    }

    @Override
    public boolean containsPlugRun(String pluginId) {

        return pluginWrapperHashMap.containsKey(pluginId);
    }

    @Override
    public List<PluginWrapper> getPluginRunning() {
        return new ArrayList<>(pluginWrapperHashMap.values());
    }

    @Override
    public void autoLoadInstallPlug() throws IOException, PluginLoadException {
        //启动已安装的插件
        Collection<PluginDbInfo> values = pluginDb.read().values();
        for (PluginDbInfo value : values) {
            if (value.state == PluginDbInfo.StateEnum.RUNNING) {
                start(value.id);
            }
        }
        if (DevPluginRun.getDevPluginClassLoader()!=null) {
            //开发模式加载 开发的插件
            Class<?> plugDevClass = DevPluginRun.getPlugDevClass();
            DevPluginClassLoader devPluginClassLoader = DevPluginRun.getDevPluginClassLoader();
            PluginWrapper pluginWrapper = devPluginLoader.load(devPluginClassLoader.getClassFile());
            pluginWrapper.start();
            pluginWrapperHashMap.put(pluginWrapper.getPlug().id(),pluginWrapper);

        }


    }


    @Override
    public void start(String pluginId) throws IOException, PluginLoadException {
        PluginDbInfo pluginDbInfo = pluginDb.read().get(pluginId);

        String startPath = pluginDb.start(pluginId);
        File file = new File(startPath);
        PluginLoader pluginLoader = getPluginLoader(file);
        PluginWrapper load = pluginLoader.load(file);
        pluginWrapperHashMap.put(pluginId, load);
        String name = file.getName();
        load.setDataPath(pluginProperties.getPath()+File.separator+"data"+File.separator+name);
        load.start();
    }

    @Override
    public void stop(String pluginId) throws IOException {
        if (pluginWrapperHashMap.containsKey(pluginId)) {
            PluginWrapper remove = pluginWrapperHashMap.remove(pluginId);
            pluginDb.stop(pluginId);
            remove.stop();
        } else {
            log.warn("插件 {} 未运行", pluginId);
        }
    }

    @Override
    public void uninstall(String pluginId) throws IOException {
        if (pluginDb.isInstall(pluginId)) {
            pluginDb.uninstall(pluginId);
            PluginWrapper remove = pluginWrapperHashMap.remove(pluginId);
            if (remove != null) {
                remove.uninstall();
            }
        } else {
            log.warn("插件 {} 未安装", pluginId);
        }
    }

    @Override
    @EventListener
    public void sysStop(ContextClosedEvent batchSendCouponEvent) {
        log.info("系统停止。");
        for (PluginWrapper plugin : pluginWrapperHashMap.values()) {
            plugin.stop();
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {

    }

    /**
     * 根据文件 获取可加载的加载器
     *
     * @param file
     * @return
     */
    private PluginLoader getPluginLoader(File file) {
        JarPluginLoader jarPluginLoader = applicationContext.getBean(JarPluginLoader.class);
//        if (jarPluginLoader.isApplicable(file)) {
//            return jarPluginLoader;
//        }
        return jarPluginLoader;

    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        //创建插件目录
        String path = pluginProperties.getPath();
        File file = new File(path);
        if (!file.exists()) {
            file.mkdirs();
        }
        //创建插件的数据目录
        File pluginData = new File(path+File.separator+"data");
        if (!pluginData.exists()) {
            file.mkdirs();
        }
        autoLoadInstallPlug();
    }
}
